/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief ImageContainer class handling type conversions and stuff - Implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include <stdio.h>
#include <assert.h>
#include "ImageContainer.hpp"
#include "owndebug.h"
#include <sys/stat.h>

//*****************************************************************************
//*****************************************************************************
using namespace MaCI::Image;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
/// Table for Image type descriptions (mapping from index to string)
static const std::string imageDataTypeDescriptionArray[] = {
  "Invalid/Unknown",
  "JPEG",
  "PNG",
  "RGB",
  "YUV420P",
  "YUV422",
  "Greyscale",
  "Raw"
};
static const int imageDataTypeDescriptionArraySize = 8;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
namespace MaCI {
  namespace Image {
    const std::string &GetImageDataTypeDescription(const EImageDataType aType)
    {
      if (aType >= 0 && aType < imageDataTypeDescriptionArraySize) {
        return imageDataTypeDescriptionArray[aType];
        
      } else {
        return imageDataTypeDescriptionArray[0];
        
      }
    }
  }
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CImageContainer::CImageContainer(void)
  : imageinfo(),
    data(NULL),
    size(0),
    isOwnedBuffer(false),
    iConversion()
{
}
//*****************************************************************************

CImageContainer::CImageContainer(const TImageInfo &aInfo,
                                 const unsigned char *aData,
                                 const unsigned int aDataSize,
                                 const bool aDuplicate)
  : imageinfo(aInfo),
    data(const_cast<unsigned char *>(aData)),
    size(aDataSize),
    isOwnedBuffer(false),
    iConversion()
{
  if (aDuplicate) {
    data = new unsigned char [ aDataSize ];
    memcpy(this->data, aData, aDataSize);
    isOwnedBuffer = true;
  }
}
//*****************************************************************************

CImageContainer::CImageContainer(const CImageContainer &aImgData) 
  : imageinfo(),
    data(),
    size(),
    isOwnedBuffer(),
    iConversion()
{
  this->Copy(aImgData);
}
//*****************************************************************************

CImageContainer &CImageContainer::operator=(CImageContainer &aImgData)
{
  this->Assign(aImgData);
  return *this;
}
//*****************************************************************************

CImageContainer &CImageContainer::operator=(const CImageContainer &aImgData)
{
  this->Copy(aImgData);
  return *this;
}
//*****************************************************************************

CImageContainer::~CImageContainer()
{
  Reset();
}
//*****************************************************************************

void CImageContainer::Reset(void)
{
  if (isOwnedBuffer && data != NULL) {
    delete[] data;
  }
  data = NULL;
  size = 0;
  isOwnedBuffer = false;
}
//*****************************************************************************

void CImageContainer::Assign(CImageContainer &aImgData)
{
  // Call reset
  Reset();

  // Take
  imageinfo = aImgData.imageinfo;
  data = aImgData.data;
  size = aImgData.size;
  isOwnedBuffer = aImgData.isOwnedBuffer;
  iConversion = aImgData.iConversion;
  
  // Invalidate
  aImgData.data = NULL;
  aImgData.size = 0;
  aImgData.isOwnedBuffer = false;
}
//*****************************************************************************

void CImageContainer::Copy(const CImageContainer &aImgData)
{
  // Call reset
  Reset();
  
  // Assign external
  this->imageinfo = aImgData.imageinfo;
  this->size = aImgData.size;
  this->data = new unsigned char [ this->size ];
  memcpy(this->data, aImgData.data, this->size);
  this->isOwnedBuffer = true;
  // Ignore iConversion here.
}
//*****************************************************************************

bool CImageContainer::WriteImageToFile(const std::string &aFilename) 
{

  bool result = false;
  dPrint(4,"Attempting to write %u bytes of data to file '%s'", 
         this->GetImageDataSize(), aFilename.c_str());
  
  // Now go.
  FILE *f = fopen(aFilename.c_str(), "wb");
  if (f != NULL) {
    size_t elements_written = fwrite(this->GetImageDataPtr(), this->GetImageDataSize(), 1, f);
    if (elements_written != 1) {
      dPrintLE(1,"Warning: Failed to write to file '%s'!", aFilename.c_str());

    } else {
      result = true;

    }
    fclose(f);

  } else {
    dPrintLE(1,"Warning: Failed to write to file '%s'!", aFilename.c_str());

  }

  // Return result
  return result;
}
//*****************************************************************************

bool CImageContainer::SetImageDataFromFile(const std::string &aFilename,
                                           const EImageDataType aSrcType,
                                           const EImageDataType aDestType)
{
  bool result = false;

  // stat() file.
  struct stat stats;
  int s = stat(aFilename.c_str(), &stats);
  if (s == 0) {
    if (S_ISREG(stats.st_mode) && access(aFilename.c_str(), R_OK) == 0) {
      unsigned char *buf = new unsigned char [stats.st_size];
      if (buf) {
        // Got access and buf ready, so open for read.
        FILE *f = fopen(aFilename.c_str(), "rb");
        if (f) {
          // Got access to regular file and buf is ready. Do read!
          size_t r = fread(buf, stats.st_size, 1, f);
          if (r == 1) {
            // Got data from file. Now; check whether source type is known.
            EImageDataType image_type;
            if (aSrcType == KImageDataUnknown) {
              // Attempt to guess.
              if (aFilename.rfind(".jpeg") > 0 ||
                  aFilename.rfind(".jpg") > 0 ||
                  aFilename.rfind(".JPG") > 0) {
                dPrint(ODVINFO,"Guess this type is 'jpeg'");
                image_type = KImageDataJPEG;
                
              } else if (aFilename.rfind(".png") > 0 ||
                         aFilename.rfind(".PNG") > 0) {
                dPrint(ODVINFO,"Guess this type is 'png'");
                image_type = KImageDataPNG;

              } else {
                dPrint(ODWARN,"Unable to guess filetype for '%s', so we'll have to give up loading.");
                image_type = KImageDataUnknown;
              }
              
            } else {
              // Assign what was ordered.
              image_type = aSrcType;

            }


            // Now, if we have some kind of guess about the file type,
            // proceed to load it with setimagedata function with
            // appropriate parameters. Notice the width/height Which
            // are not specified: Loaders for 'fully defined' types
            // don't need these, so its safe to omit them here.
            if (image_type != KImageDataUnknown) {
              
              // Finally; call another assign function to do the real work.
              result = SetImageData(buf, stats.st_size, image_type, 0, 0, aDestType, true);

              // Release the temporary buffer here.
              delete[] buf;
              buf = NULL;
            }

          } else {
            dPrint(ODWARN,"Even all precautions taken, failed to read the file. :/ Cannot continue.");

          }
          
          fclose(f);
        } else {
          dPrint(ODWARN,"Read failed despite all prechecks? Well, nothing to do anymore.");

        }
      } else {
        // No buf
        dPrint(ODWARN,"Failed to allocate memory! (Requested %u bytes, which failed)", 
               stats.st_size);

      }
    } else {
      // No access or not regular.
      dPrint(ODWARN,"Tried to open Imagefile '%s' which is not regular or not accessible.", 
             aFilename.c_str());

    }
    
  }
  
  
  // Status
  if (!result) {
    dPrint(ODWARN, "Failed to load imageFile '%s'", aFilename.c_str());
  }




  return result;
}
//*****************************************************************************

bool CImageContainer::SetImageData(const unsigned char *aData,
                                   const unsigned int aSize,
                                   const EImageDataType aSrcType,
                                   const unsigned int aWidth, 
                                   const unsigned int aHeight,
                                   const EImageDataType aDestType,
                                   const bool aDuplicateSource)
{
  bool result = true;
  // Conversion buffer container.
  unsigned char *convertBuffer = NULL;
  unsigned int convertBufferSize = 0;

  // Active buffer pointer.
  unsigned char **activeBufferPtr = const_cast<unsigned char **>(&aData);
  unsigned int *activeBufferSizePtr = const_cast<unsigned int *>(&aSize);

  // Extra flag. This is required for tracking whether buffer was
  // already duplicated by conversion routine.
  bool need_duplicate = aDuplicateSource;


  // Construct ImageInfo - assume in this point that the function
  // succeeds.
  TImageInfo info(aWidth, aHeight,
                  (aDestType==KImageDataUnknown) ? aSrcType : aDestType);
  //  info.Print(4);


  // First step; attempt conversion.
  if (aDestType != KImageDataUnknown && 
      aSrcType != aDestType) {
    // If dest-type is not 'Unknown' AND srctype != desttype, we will
    // have to attempt conversion.
    unsigned int width = aWidth;
    unsigned int height = aHeight;
    result = iConversion.ConvertImageData(aSrcType, aDestType,
                                          width, height,
                                          *activeBufferPtr, *activeBufferSizePtr,
                                          &convertBuffer, &convertBufferSize);
  
    if (result) {
      // 
      if ( width != aWidth || height != aHeight ) {
        dPrint(ODWARN,"Warning; ImageDecoder returned dimensions %dx%d while instructed as %dx%d",
               width, height, imageinfo.imagewidth, imageinfo.imageheight);

        if (aWidth == 0) {
          dPrint(ODVINFO,"Reassigning Width to value returned by decoder (%d -> %d)", aWidth, width);
          info.imagewidth = width;
        }
        if (aHeight == 0) {
          dPrint(ODVINFO,"Reassigning Height to value returned by decoder (%d -> %d)", aHeight, height);
          info.imageheight = height;
        }
      }

      // Conversion succesfull! Assign active buffer to point to convertBuffer.
      activeBufferPtr = &convertBuffer;
      activeBufferSizePtr = &convertBufferSize;
      if (need_duplicate) need_duplicate = false;
      dPrint(10,"Succesfully converted %u(%s) -> %u(%s)!", 
             aSrcType, GetImageDataTypeDescription(aSrcType).c_str(), 
             aDestType, GetImageDataTypeDescription(aDestType).c_str());
      
    } else {
      // Conversion failed!
      dPrint(3,"Conversion %u(%s) -> %u(%s) failed!", 
             aSrcType, GetImageDataTypeDescription(aSrcType).c_str(),
             aDestType, GetImageDataTypeDescription(aDestType).c_str());
      
    }
  }


  // Are we still OK? If, then proceed with packaging.
  if (result) {

    // Cleanup if previous buffer exists.
    if (this->data != NULL && isOwnedBuffer) delete[] this->data;

    // Copy imageinfo.
    imageinfo = info;

    if (need_duplicate) {
      this->size = *activeBufferSizePtr;
      this->data = new unsigned char [ this->size ];
      memcpy(this->data, *activeBufferPtr, this->size);
      isOwnedBuffer = true;
      
    } else {
      this->data = *activeBufferPtr;
      this->size = *activeBufferSizePtr;
      
      // Now check whether the active buffer is really the input
      // buffer; if it is, mark buffer unowned.
      isOwnedBuffer = (*activeBufferPtr == aData) ? false : true;

    }
  }


  // Cleanup of temporary buffers (verify that they are not stored
  // inside instance)
  if (convertBuffer && this->data != convertBuffer) {
    delete[] convertBuffer;
  }

  // Last but not least - return result! :)
  return result;
}
//*****************************************************************************

bool CImageContainer::ConvertTo(const EImageDataType aDestType)
{
  bool result = false;
  unsigned char *convertBuffer = NULL;
  unsigned int convertBufferSize = 0;


  // Do some basic tests.
  if (imageinfo.imagedatatype != KImageDataUnknown &&
      imageinfo.imagedatatype != aDestType &&
      this->data != NULL &&
      this->size > 0) {
    
    // Attempt conversion.
    unsigned int width = imageinfo.imagewidth;
    unsigned int height = imageinfo.imageheight;
    result = iConversion.ConvertImageData((EImageDataType)imageinfo.imagedatatype, aDestType,
                                          width, height,
                                          this->data, this->size,
                                          &convertBuffer, &convertBufferSize);
    
    if (result) {
      // Check and warn
      if ( width != imageinfo.imagewidth || height != imageinfo.imageheight ) {
        dPrint(ODWARN,"Warning; ImageDecoder returned dimensions %dx%d while instructed as %dx%d",
               width, height, imageinfo.imagewidth, imageinfo.imageheight);
        
        
        if (imageinfo.imagewidth == 0) {
          dPrint(ODVINFO,"Reassigning Width to value returned by decoder (%d -> %d)", imageinfo.imagewidth, width);
          imageinfo.imagewidth = width;
        }
        if (imageinfo.imageheight == 0) {
          dPrint(ODVINFO,"Reassigning Height to value returned by decoder (%d -> %d)", imageinfo.imageheight, height);
          imageinfo.imageheight = height;
        }
      }

      // Conversion succesfull!
      dPrint(10,"Succesfully converted %s(%u) -> %s(%u)!", 
             GetImageDataTypeDescription((EImageDataType)imageinfo.imagedatatype).c_str(),
             imageinfo.imagedatatype, 
             GetImageDataTypeDescription(aDestType).c_str(),
             aDestType);
      
      // Do housekeeping
      imageinfo.imagedatatype = aDestType;
      if (isOwnedBuffer) delete[] this->data;
      this->data = convertBuffer;
      this->size = convertBufferSize;
      isOwnedBuffer = true;
      
    } else {
      // Conversion failed!
      dPrint(3,"Conversion %s(%u) -> %s(%u) failed!", 
             GetImageDataTypeDescription((EImageDataType)imageinfo.imagedatatype).c_str(),
             imageinfo.imagedatatype, 
             GetImageDataTypeDescription(aDestType).c_str(),
             aDestType);
      
    }
    
  } else if (imageinfo.imagedatatype == aDestType) {
    // No conversion required.
    dPrint(10,"No conversion required, Source/Target types match ('%s')",
           GetImageDataTypeDescription(aDestType).c_str());
    result = true;

  } else {
    dPrint(2,"Container in invalid state - cannot convert!");
    dPrint(2,"Containerstate:\n"
           "\timageinfo.imagedatatype != KImageDataUnknown == %d\n"
           "\tthis->data != NULL == %d\n"
           "\tthis->size > 0 == %d\n",
           imageinfo.imagedatatype != KImageDataUnknown,
           this->data != NULL,
           this->size > 0);

  }

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

